#pragma once
// Copyright (c) 2015 Tony Kirke. License MIT  (http://www.opensource.org/licenses/mit-license.php)
// from directory: spuc_templates
#include <spuc/spuc_types.h>
#include <cstdlib>
#include <iostream>
#ifdef SPUC_BOUNDS_CHECK
#include <cassert>
#endif
namespace SPUC {
/*
*
* Template Numerical Toolkit (TNT)
*
* Mathematical and Computational Sciences Division
* National Institute of Technology,
* Gaithersburg, MD USA
*
*
* This software was developed at the National Institute of Standards and
* Technology (NIST) by employees of the Federal Government in the course
* of their official duties. Pursuant to title 17 Section 105 of the
* United States Code, this software is not subject to copyright protection
* and is in the public domain. NIST assumes no responsibility whatsoever for
* its use by other parties, and makes no guarantees, expressed or implied,
* about its quality, reliability, or any other characteristic.
*
*/
//! \file
//! \brief Templated one-dimensional, numerical array which
//!	looks like a conventional C array.
//
/**
        Templated one-dimensional, numerical array which
        looks like a conventional C array.
        Elements are accessed via the familiar A[i] notation.

        <p>
        Array assignment is by reference (i.e. shallow assignment).
        That is, B=A implies that the A and B point to the
        same array, so modifications to the elements of A
        will be reflected in B. If an independent copy
        is required, then B = A.copy() can be used.  Note
        that this facilitates returning arrays from functions
        without relying on compiler optimizations to eliminate
        extensive data copying.

        <p>
        The indexing and layout of this array object makes
        it compatible with C and C++ algorithms that utilize
        the familiar C[i] notation.  This includes numerous
        textbooks, such as Numercial Recipes, and various
        public domain codes.

        <p>
        This class employs its own garbage collection via
        the use of reference counts.  That is, whenever
        an internal array storage no longer has any references
        to it, it is destoryed.
*/
template <class T> class array1d {
 private:
  T* v_;
  int n_;
  int* ref_count_;

  void initialize_(int n);
  void copy_(T* p, const T* q, int len) const;
  void set_(const T& val);
  void destroy_();
  inline const T* begin_() const;
  inline T* begin_();

 public:
  typedef T value_type;

  array1d();
  explicit array1d(int n);
  array1d(int n, T* a);
  array1d(int n, const T& a);
  inline array1d(const array1d& A);
  inline array1d& operator=(const T& a);
  inline array1d& operator=(const array1d& A);
  inline array1d& ref(const array1d& A);
  array1d copy() const;
  array1d& inject(const array1d& A);
  inline T& operator[](int i);
  inline const T& operator[](int i) const;
  inline int dim1() const;
  inline int dim() const;
  inline int ref_count() const;
  ~array1d();
  // NEW
  array1d<T> mid(int s, int nr) const;
  void replace_mid(int s, const array1d<T>& v) const;
  inline int length() const;
  array1d<T>& set_size(int m, bool f) { return (set_size(m)); }
  array1d<T>& newsize(int m, bool f) { return (set_size(m)); }
  array1d<T>& newsize(int m) { return (set_size(m)); }
  array1d<T>& set_size(int m) {
    if (dim1() == m) return *this;
    destroy_();
    initialize_(m);
    return *this;
  }
  // NEW
};
/**
        Null constructor.  Creates a 0-length (NULL) array.
        (Reference count is also zero.)
*/

template <class T> array1d<T>::array1d() : v_(0), n_(0), ref_count_(0) {
  ref_count_ = new int;
  *ref_count_ = 1;
}

/**
        Copy constructor. Array data is NOT copied, but shared.
        Thus, in array1d B(A), subsequent changes to A will
        be reflected in B.  For an indepent copy of A, use
        array1d B(A.copy()), or B = A.copy(), instead.
*/
template <class T> array1d<T>::array1d(const array1d<T>& A) : v_(A.v_), n_(A.n_), ref_count_(A.ref_count_) {
  (*ref_count_)++;
}

/**
        Create a new array (vector) of length <b>n</b>,
        WIHOUT initializing array elements.
        To create an initialized array of constants, see array1d(n,value).

        <p>
        This version avoids the O(n) initialization overhead and
        is used just before manual assignment.

        @param n the dimension (length) of the new matrix.
*/
template <class T> array1d<T>::array1d(int n) : v_(0), n_(n), ref_count_(0) {
  initialize_(n);
  ref_count_ = new int;
  *ref_count_ = 1;
}

/**
        Create a new array of length <b>n</b>,  initializing array elements to
        constant specified by argument.  Most often used to
        create an array of zeros, as in A(n, 0.0).

        @param n the dimension (length) of the new matrix.
        @param val the constant value to set all elements of the new array to.
*/
template <class T> array1d<T>::array1d(int n, const T& val) : v_(0), n_(n), ref_count_(0) {
  initialize_(n);
  set_(val);
  ref_count_ = new int;
  *ref_count_ = 1;
}

/**
        Create a new n-length array,  as a view of an existing one-dimensional
        C array.  (Note that the storage for this pre-existing array will
        never be destroyed by the Aray1DRef class.)

        @param n the dimension (length) of the new matrix.
        @param a the one dimensional C array to use as data storage for
                the array.
*/
template <class T> array1d<T>::array1d(int n, T* a) : v_(a), n_(n), ref_count_(0) {
  ref_count_ = new int;
  *ref_count_ = 2; /* this avoid destorying original data. */
}

/**
        A[i] indexes the ith element of A.  The first element is
        A[0]. If SPUC_BOUNDS_CHECK is defined, then the index is
        checked that it falls within the array bounds.
*/
template <class T> inline T& array1d<T>::operator[](int i) {
#ifdef SPUC_BOUNDS_CHECK
  assert(i >= 0);
  assert(i < n_);
#endif
  return v_[i];
}

/**
        A[i] indexes the ith element of A.  The first element is
        A[0]. If SPUC_BOUNDS_CHECK is defined, then the index is
        checked that it fall within the array bounds.
*/
template <class T> inline const T& array1d<T>::operator[](int i) const {
#ifdef SPUC_BOUNDS_CHECK
  assert(i >= 0);
  assert(i < n_);
#endif
  return v_[i];
}

/**
        Assign all elemnts of A to a constant scalar.
*/
template <class T> array1d<T>& array1d<T>::operator=(const T& a) {
  set_(a);
  return *this;
}
/**
        Create a new of existing matrix.  Used in B = A.copy()
        or in the construction of B, e.g. array1d B(A.copy()),
        to create a new array that does not share data.

*/
template <class T> array1d<T> array1d<T>::copy() const {
  array1d A(n_);
  copy_(A.begin_(), begin_(), n_);

  return A;
}

/**
        Copy the elements to from one array to another, in place.
        That is B.inject(A), both A and B must conform (i.e. have
        identical row and column dimensions).

        This differs from B = A.copy() in that references to B
        before this assignment are also affected.  That is, if
        we have
        <pre>
        array1d A(n);
        array1d C(n);
        array1d B(C);        // elements of B and C are shared.

</pre>
        then B.inject(A) affects both and C, while B=A.copy() creates
        a new array B which shares no data with C or A.

        @param A the array from which elements will be copied
        @return an instance of the modifed array. That is, in B.inject(A),
        it returns B.  If A and B are not conformat, no modifications to
        B are made.

*/
template <class T> array1d<T>& array1d<T>::inject(const array1d& A) {
  if (A.n_ == n_) copy_(begin_(), A.begin_(), n_);

  return *this;
}

/**
        Create a reference (shallow assignment) to another existing array.
        In B.ref(A), B and A shared the same data and subsequent changes
        to the array elements of one will be reflected in the other.
        <p>
        This is what operator= calls, and B=A and B.ref(A) are equivalent
        operations.

        @return The new referenced array: in B.ref(A), it returns B.
*/
template <class T> array1d<T>& array1d<T>::ref(const array1d<T>& A) {
  if (this != &A) {
    (*ref_count_)--;
    if (*ref_count_ < 1) { destroy_(); }

    n_ = A.n_;
    v_ = A.v_;
    ref_count_ = A.ref_count_;

    (*ref_count_)++;
  }
  return *this;
}

/**
        B = A is shorthand notation for B.ref(A).
*/
template <class T> array1d<T>& array1d<T>::operator=(const array1d<T>& A) { return ref(A); }

/**
        @return the dimension (number of elements) of the array.
        This is equivalent to dim() and dim1().
*/
template <class T> inline int array1d<T>::dim1() const { return n_; }

template <class T> inline int array1d<T>::length() const { return n_; }

/**
        @return the dimension (number of elements) of the array.
        This is equivalent to dim1() and dim1().
*/
template <class T> inline int array1d<T>::dim() const { return n_; }

/**
        @return the number of arrays that share the same storage area
        as this one.  (Must be at least one.)
*/
template <class T> inline int array1d<T>::ref_count() const { return *ref_count_; }

template <class T> array1d<T>::~array1d() {
  (*ref_count_)--;

  if (*ref_count_ < 1) destroy_();
}

/* private internal functions */

template <class T> void array1d<T>::initialize_(int n) {
  v_ = new T[n];
  n_ = n;
}

template <class T> void array1d<T>::set_(const T& a) {
  T* begin = &(v_[0]);
  T* end = begin + n_;

  for (T* p = begin; p < end; p++) *p = a;
}

template <class T> void array1d<T>::copy_(T* p, const T* q, int len) const {
  T* end = p + len;
  while (p < end) *p++ = *q++;
}

template <class T> void array1d<T>::destroy_() {
  if (v_ != 0) { delete[](v_); }

  if (*ref_count_ != 0) delete ref_count_;
}

/**
        @returns location of first element, i.e. A[0] (mutable).
*/
template <class T> const T* array1d<T>::begin_() const { return &(v_[0]); }

/**
        @returns location of first element, i.e. A[0] (mutable).
*/
template <class T> T* array1d<T>::begin_() { return &(v_[0]); }

template <class T> array1d<T> array1d<T>::mid(int s, int nr) const {
  array1d A(nr);
  for (int i = 0; i < nr; i++) A[i] = v_[s + i];
  return A;
}
template <class T> void array1d<T>::replace_mid(int s, const array1d<T>& v) const {
  for (int i = 0; i < v.dim(); i++) v_[s + i] = v[i];
}
}  // namespace SPUC
